package JDK常用类库.stream;

//import lombok.Data;
import org.junit.Test;

import java.util.Objects;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * @Author：russ
 * @Date：2023/8/14
 * @apiNote：
 */
//@Data
public class StreamAPITest1 {
    /**
     * forEach用来遍历流中的数据的。
     * 该方法接受一个Consumer接口，会将每一个流元素交给函数处理
     */
    @Test
    public void test1(){
        Stream.of("a1", "a2", "a3").forEach(System.out::println);
    }

    /**
     * Stream流中的count方法用来统计其中的元素个数的。
     * 该方法返回一个long值，代表元素的个数。
     */
    @Test
    public void test2(){
        long count = Stream.of("a1", "a2", "a3").count();
        System.out.println(count);
    }

    /**
     * filter方法的作用是用来过滤数据的。返回符合条件的数据。可以通过filter方法将一个流转换成另一个子集流。
     * 该接口接收一个Predicate函数式接口参数作为筛选条件。
     */
    @Test
    public void test3(){
        Stream.of("a1", "a2", "a3","bb","cc","aa","dd")
                .filter(s -> s.contains("a"))
                .forEach(System.out::println);
    }

    /**
     * limit方法可以对流进行截取处理，支取前n个数据。
     * 参数是一个long类型的数值，如果集合当前长度大于参数就进行截取，否则不操作。
     */
    @Test
    public void test4(){
        Stream.of("a1", "a2", "a3","bb","cc","aa","dd")
                .limit(3)
                .forEach(System.out::println);
    }

    /**
     * 如果希望跳过前面几个元素，可以使用skip方法获取一个截取之后的新流。
     */
    @Test
    public void test5(){
        Stream.of("a1", "a2", "a3","bb","cc","aa","dd")
                .skip(3)
                .forEach(System.out::println);
    }

    /**
     * 如果我们需要将流中的元素映射到另一个流中，可以使用map方法。
     * 该接口需要一个Function函数式接口参数，可以将当前流中的T类型数据转换为另一种R类型的数据。
     * flatMap：将多层数据转换为单层数据操作
     *
     * 两者区别看这篇文章：https://blog.csdn.net/weixin_52772307/article/details/128944511
     * 当我们需要将具有层级结构的数据展平时，也就是将多层数据转换为单层数据操作时，我们可以使用 flatMap 方法。如果我们只是简单的对流中的数据计算或者转换时，可以使用 map 方法。
     *
     * 总结举例：
     * 1.使用 flatMap：[a，b，c，d，[e，f [g，h，i]]] 转换为 [a，b，c，d，e，f，g，h，i]
     * 2.使用 map: [1，2，3，4，5，6] 转换为 [11，12，13，14，15，16]
     * 3.使用 map: [a，b，c] 转换为 [A，B，C]
     */
    @Test
    public void test6(){
        Stream.of("1", "2", "3","4","5","6","7")
                //.map(msg -> Integer.parseInt(msg))
                .map(Integer::parseInt)
                .forEach(System.out::println);
    }

    /**
     * 如果需要将数据排序，可以使用sorted方法。
     * 在使用的时候可以根据自然规则排序，也可以通过比较强来指定对应的排序规则。
     */
    @Test
    public void test7(){
        Stream.of("1", "3", "2","4","0","9","7")
                //.map(msg -> Integer.parseInt(msg))
                .map(Integer::parseInt)
                .sorted((o1,o2) -> (o2 - o1)) // 根据比较强指定排序规则
                .forEach(System.out::println);
    }

    /**
     * 如果要去掉重复数据，可以使用distinct方法。
     * Stream流中的distinct方法对于基本数据类型是可以直接去重的，
     * 但是对于自定义类型，我们是需要重写hashCode和equals方法来移除重复元素。
     */
    @Test
    public void test8(){
        Stream.of("1", "3", "3","4","0","1","7")
                //.map(msg -> Integer.parseInt(msg))
                .map(Integer::parseInt)
                //.sorted() // 根据数据的自然顺序排序
                .sorted((o1,o2) -> o2 - o1) //根据比较强指定排序规则
                .distinct()
                .forEach(System.out::println);
        System.out.println("--------");
        Stream.of(
                new Person("张三",18),
                new Person("李四",22),
                new Person("张三",18)
        ).distinct()
                .forEach(System.out::println);
    }

    /**
     * match是一个终结方法！！！！！！！！！！！！！！！！！！！！！！！！！
     * 如果需要判断数据是否匹配指定的条件，可以使用match相关的方法。
     *
     * boolean anyMatch(Predicate<? super T> predicate); // 元素是否有任意一个满足条件
     * boolean allMatch(Predicate<? super T> predicate); // 元素是否都满足条件
     * boolean noneMatch(Predicate<? super T> predicate); // 元素是否都不满足条件
     */
    @Test
    public void test9(){
        boolean b = Stream.of("1", "3", "3", "4", "5", "1", "7")
                .map(Integer::parseInt)
                //.allMatch(s -> s > 0)
                //.anyMatch(s -> s > 4)
                .noneMatch(s -> s > 4);
        System.out.println(b);
    }

    /**
     * 如果需要找到某些数据，可以使用find方法来实现
     *
     * 注：
     * findFirst就是找第一个元素
     * findAny是找任意一个元素；如果对集合做流化处理使用的是串行流，那findAny的任意一个都是第一个；如果使用并行流，就是任意一个元素
     */
    @Test
    public void test10(){
        Optional<String> first = Stream.of("1", "3", "3", "4", "5", "1", "7").findFirst();
        System.out.println(first.get());

        Optional<String> any = Stream.of("1", "3", "3", "4", "5", "1", "7").findAny();
        System.out.println(any.get());
    }

    /**
     * 如果我们想要获取最大值和最小值，那么可以使用max和min方法。
     */
    @Test
    public void test11(){
        Optional<Integer> max = Stream.of("1", "3", "3", "4", "5", "1", "7")
                .map(Integer::parseInt)
                .max((o1,o2) -> o1 - o2);
        System.out.println(max.get());

        Optional<Integer> min = Stream.of("1", "3", "3", "4", "5", "1", "7")
                .map(Integer::parseInt)
                .min((o1,o2) -> o1 - o2);
        System.out.println(min.get());
    }

    /**
     * 如果需要将所有数据归纳得到一个数据，可以使用reduce方法
     */
    @Test
    public void test12(){
        Integer sum = Stream.of(4, 5, 3, 9)
                // identity：默认值
                // 第一次的时候会将默认值赋值给x
                // 之后每次会将 上一次的操作结果赋值给x, y就是每次从数据中获取的元素
                .reduce(0, (x, y) -> {
                    System.out.println("x = " + x + ",y = " + y);
                    return x + y;
                });
        System.out.println(sum);
        // 获取最大值
        Integer max = Stream.of(4, 5, 3, 9)
                .reduce(0, (x, y) -> {
                    return x > y ? x : y;
                });
        System.out.println(max);
    }

    /**
     * 在实际开发中我们经常会将map和reduce一块来使用
     */
    @Test
    public void test13(){
        // 1.求出所有年龄的总和
        Integer sumAge = Stream.of(
                        new Person("张三", 18)
                        , new Person("李四", 22)
                        , new Person("张三", 13)
                        , new Person("王五", 15)
                        , new Person("张三", 19)
                ).map(Person::getAge) // 实现数据类型的转换
                .reduce(0, Integer::sum);
        System.out.println(sumAge);

        // 2.求出所有年龄中的最大值
        Integer maxAge = Stream.of(
                        new Person("张三", 18)
                        , new Person("李四", 22)
                        , new Person("张三", 13)
                        , new Person("王五", 15)
                        , new Person("张三", 19)
                ).map(Person::getAge) // 实现数据类型的转换，符合reduce对数据的要求
                .reduce(0, Math::max); // reduce实现数据的处理
        System.out.println(maxAge);

        // 3.统计字符 a 出现的次数
        Integer count = Stream.of("a", "b", "c", "d", "a", "c", "a")
                .map(ch -> "a".equals(ch) ? 1 : 0)
                .reduce(0, Integer::sum);
        System.out.println(count);
    }

    /**
     * 如果需要将Stream中的Integer类型转换成int类型，可以使用mapToInt方法来实现
     */
    @Test
    public void test14(){
        // Integer占用的内存比int多很多，在Stream流操作中会自动装修和拆箱操作
        Integer arr[] = {1,2,3,5,6,8};
        Stream.of(arr)
                .filter(i -> i > 0)
                .forEach(System.out::println);
        System.out.println("---------");

        // 为了提高程序代码的效率，可以先将流中Integer数据转换为int数据，然后再操作
        IntStream intStream = Stream.of(arr)
                .mapToInt(Integer::intValue);
        intStream.filter(i -> i > 3)
                .forEach(System.out::println);
    }

    /**
     * 如果有两个流，希望合并成为一个流，那么可以使用Stream接口的静态方法concat。
     */
    @Test
    public void test15(){
        Stream<String> stream1 = Stream.of("a","b","c");
        Stream<String> stream2 = Stream.of("x", "y", "z");
        // 通过concat方法将两个流合并为一个新的流
        Stream.concat(stream1,stream2).forEach(System.out::println);
    }
}
class Person{
    private String name;
    private int age;

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    private int height;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Person(String name, int age, int height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Person() {

    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && height == person.height && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, height);
    }
}
